package thisgame.model;

import java.util.Formatter;

/**
 * 2차원 벡터 클래스.
 * 이 클래스는 불변(immutable) 객체이다.
 * 
 * @author Kroisse
 *
 */
public class Vector2D {
	/**
	 * 실수 계산에서 발생하는 오차를 보정하기 위해 내부적으로 사용하는 입실론 값.
	 */
	static final public double EPSILON = 1e-8;
	
	private double x, y;
	
	/**
	 * 벡터의 x축 성분과 y축 성분으로 새로운 벡터를 만든다.
	 * 
	 * @param x
	 * 			새로운 벡터의 x축 성분
	 * @param y
	 * 			새로운 벡터의 y축 성분
	 */
	public Vector2D(double x, double y) {
		this.x = x;
		this.y = y;
	}
	
	/**
	 * 벡터의 크기와 라디안으로 새로운 벡터를 만든다.
	 * 
	 * @param magnitude
	 * 			새로운 벡터의 크기
	 * @param angrad
	 * 			x축의 양의 방향을 기준으로 한 새로운 벡터의 각도. 라디안 값으로 주어져야 한다.
	 * @return 크기와 각도로 만들어진 새로운 벡터.
	 */
	static public Vector2D makeWithMagnitudeAndRadians(double magnitude, double angrad) {
		double x = magnitude * Math.cos(angrad);
		double y = magnitude * Math.sin(angrad);
		return new Vector2D(x, y);
	}
	
	/**
	 * 벡터의 x축 성분을 구한다.
	 * 
	 * @return 벡터의 x축 성분.
	 */
	public double getX() {
		return x;
	}
	
	/**
	 * 벡터의 y축 성분을 구한다.
	 * 
	 * @return 벡터의 y축 성분.
	 */
	public double getY() {
		return y;
	}
	
	/**
	 * 벡터의 크기를 구한다.
	 * 
	 * @return 벡터의 크기(원점으로부터의 길이).
	 */
	public double getMagnitude() {
		double x = getX(), y = getY();
		return Math.sqrt(x*x + y*y);
	}
	/**
	 * 벡터의 라디안을 구한다.
	 * 
	 * @return x축의 양의 방향을 기준으로 한 벡터의 각도. 라디안 값으로 주어진다.
	 */
	public double getRadians() {
		return Math.atan2(getY(), getX());
	}
	
	/**
	 * 벡터의 각도를 구한다.
	 * 
	 * @return x축의 양의 방향을 기준으로 한 벡터의 각도. 
	 */
	public double getDegrees() {
		return Math.toDegrees(getRadians());
	}
	
	/**
	 * 음의 벡터를 구한다.
	 * 
	 * @return 원래의 벡터와 방향이 반대인 새로운 벡터.
	 */
	public Vector2D negated() {
		return this.mul(-1);
	}
	
	/**
	 * 벡터의 합을 구한다.
	 * 
	 * @param other
	 * 			벡터합 연산의 오른쪽 피연산자.
	 * @return <code>this</code>와 <code>other</code>를 더한 새로운 벡터.
	 */
	public Vector2D add(Vector2D other) {
		return new Vector2D(getX()+other.getX(), getY()+other.getY());
	}
	
	/**
	 * 벡터의 차를 구한다.
	 * 
	 * @param other
	 * 			벡터차 연산의 오른쪽 피연산자.
	 * @return <code>this</code>에서 <code>other</code>를 뺀 새로운 벡터.
	 */
	public Vector2D sub(Vector2D other) {
		return this.add(other.negated());
	}
	
	
	/**
	 * 벡터의 스칼라배(scalar multiplication)를 구한다.
	 *  
	 * @param c
	 * 			벡터에 곱해질 스칼라값.
	 * @return 스칼라가 곱해진 새로운 벡터.
	 */
	public Vector2D mul(double c) {
		return new Vector2D(c*getX(), c*getY());
	}
	
	/**
	 * 벡터의 내적을 구한다.
	 * 
	 * @param other
	 * 			벡터 내적 연산의 오른쪽 피연산자.
	 * @return <code>this</code>와 <code>other</code>의 내적.
	 */
	public double dotProduct(Vector2D other) {
		return getX()*other.getX() + getY()*other.getY();
	}
	
	/**
	 * 두 벡터가 동등한지 비교한다.
	 * 실수 특유의 부정확성 때문에 {@link #EPSILON EPSILON}값보다 차이가 작을 경우 같은 벡터로 간주하는데,
	 * 이 때문에 {@link java.lang.Object#equals(Object) Object#equals(Object)}의 요구조건 중 하나인 
	 * <i>추이성(transitive)</i>을 만족하지 못할 수도 있을 것으로 예상된다.
	 */
	@Override
	public boolean equals(Object other) {
		if (other == null) return false;
		if (!(other instanceof Vector2D)) return false;

		Vector2D o = (Vector2D)other;
		return Math.abs(getX() - o.getX()) < EPSILON
			&& Math.abs(getY() - o.getY()) < EPSILON;
	}
	
	@Override
	public String toString() {
		return new Formatter().format("<%d, %d>", getX(), getY()).toString();
	}
}
